<template>
  <el-dialog
    v-model="dialogVisible"
    title="上传资源"
    :width="700"
    class="custom-dialog-progress"
    :before-close="handleClose"
    :close-on-click-modal="false"
    v-loading="isWaitClose"
  >
    <div class="progress-wrap">
      <div class="title">资源名称:{{ resourceName }}</div>
      <el-progress :percentage="uploadProgress" :show-text="false" />
      <div class="state-wrap">
        <div v-if="stateCode === 0" class="progress-text">
          已上传{{ uploadedSize }}MB/{{ totalSize }}MB
        </div>
        <div v-if="stateCode === 0" class="progress-percent">{{ uploadProgress }}%</div>
        <div v-else class="state" :class="[`state_${stateCode}`]">
          {{ stateMap.get(stateCode) || '' }}
          <div class="dot" v-if="stateCode === 0 || stateCode === 1">...</div>
        </div>
      </div>
      <div class="option" v-if="stateCode === 3">
        <el-button @click="close">取消上传</el-button>
        <el-button color="#0560FD" type="primary" @click="reUpLoad">重新上传</el-button>
      </div>
      <div class="option" v-if="stateCode === 4">
        <el-button @click="close">取消处理</el-button>
        <el-button color="#0560FD" type="primary" @click="reOption">重新处理</el-button>
      </div>
      <div class="option" v-if="stateCode === 2">
        <el-button color="#0560FD" type="primary" @click="close">确认并关闭</el-button>
      </div>
    </div>
  </el-dialog>
</template>
<script setup lang="ts">
import { ref } from 'vue';

import { getResoProg, getSliceState, getUploadProg, mergerFile, upSplice } from '@/api/common';
import { confirmAgain, transferMb } from '@/utils';
import { ElMessage } from 'element-plus';
import axios from 'axios';
import { reProcess } from '@/api/resource';

interface IProps {
  resourceId: string;
  transId: string;
  resourceName: string;
  getFile: any;
  fileSize: number;
  uploadMethod: string;
}

const props = withDefaults(defineProps<IProps>(), {
  resourceId: '',
  transId: '',
  resourceName: '',
  fileSize: 0,
  uploadMethod: '',
});
const dialogVisible = ref<boolean>(true);
const emit = defineEmits(['closeHandle']);
let timer1: any = null;

const stateMap: any = new Map([
  [0, '上传中'],
  [1, '处理中'],
  [2, '已完成'],
  [3, '上传失败'],
  [4, '处理失败'],
]);

const Status = {
  wait: 'wait',
  pause: 'pause',
  uploading: 'uploading',
  error: 'error',
  done: 'done',
};

const stateCode = ref<number>(0);
/**
 * 服务器处理文件状态
 */
const stateCodeService = ref<number>(0);
const uploadProgress = ref<number>(0);
const totalSize = ref<number>(0);
const uploadedSize = computed(() => {
  return (totalSize.value * (uploadProgress.value / 100)).toFixed(2);
});

onMounted(() => {
  if (props.uploadMethod === 'local') {
    getResProgress();
  } else {
    fileUpload();
  }
});

onBeforeUnmount(() => {
  clearTimeout(timer1);
});
const isWaitClose = ref(false);
const handleClose = async (done: () => void) => {
  if (stateCode.value === 0) {
    const flag = await confirmAgain('温馨提示', '点击“取消上传”，将立即终止上传。', '取消上传', '');
    if (flag) {
      isWaitClose.value = true;
      cancelUpLoad();
      close();
      done();
    }
  } else if (stateCode.value === 1) {
    const flag = await confirmAgain(
      '温馨提示',
      '关闭窗口后，将在后台对上传资源继续进行数据处理。',
      '知道了',
      ''
    );
    if (flag) {
      isWaitClose.value = true;
      close();
      done();
    }
  } else {
    dialogVisible.value = false;
    emit('closeHandle', true);
    done();
  }
};
const cancelUpLoad = () => {
  cancelTokenMap.forEach((cancelToken: any, key: any) => {
    if (cancelToken) {
      cancelToken();
    }
  });
};
const close = () => {
  dialogVisible.value = false;
  emit('closeHandle', true);
};
function reUpLoad() {
  if (props.uploadMethod === 'local') {
    getResProgress();
  } else {
    fileUpload();
  }
}
async function reOption() {
  if (stateCodeService.value === 25) {
    // 合并失败，走重新合并逻辑
    mergerFileOption();
  } else {
    const fData = new FormData();
    fData.set('resourceId', props.resourceId);
    const param = {
      data: fData,
    };
    await reProcess(param).then((res: any) => {
      stateCode.value = 1;
      uploadProgress.value = 0;
      getResProgress();
    });
  }
}

function fileUpload() {
  const chunks = props.getFile();
  totalSize.value = transferMb(props.fileSize);
  stateCode.value = 0;
  uploadChunks(chunks);
}

async function uploadChunks(chunks = []) {
  const TotalSlice = chunks.length;
  const data = await getSliceState(props.transId, TotalSlice);
  const uploadedList = [] as any;
  if (data.isComplete) {
    stateCode.value = 1;
    mergerFileOption();
    return;
  }

  chunks.forEach((item: any, index: number) => {
    if (data.lackOfList.includes(index + 1)) {
      uploadedList.push({
        TransId: props.transId,
        Index: index + 1,
        Size: item.file.size,
        TotalSlice: TotalSlice,
        SubFile: item.file,
        status: Status.wait,
      });
    }
  });

  try {
    await sendRequest(uploadedList, 2);
    mergerFileOption();
  } catch (e) {
    stateCode.value = 3;
    ElMessage.error('上传失败，请重试');
  }
}
const cancelTokenMap = new Map();
function sendRequest(uploadedList: any, max = 4, retrys = 3) {
  return new Promise((resolve, reject) => {
    const len = uploadedList.length;
    let counter = 0;
    const retryArr: any = [];

    const start = async () => {
      // 有请求，有通道
      if (isWaitClose.value) {
        return;
      }
      while (uploadedList.length > 0 && max > 0) {
        max--; // 占用通道

        const index = uploadedList.findIndex(
          (v: any) => v.status === Status.wait || v.status === Status.error
        ); // 等待或者error
        if (index !== -1) {
          uploadedList[index].status = Status.uploading;
          console.log('切片开始:' + uploadedList[index].Index, new Date().toLocaleString());
          upSplice(
            { ...uploadedList[index] },
            (progressEvent: any) => {
              const progress = ((progressEvent.loaded / progressEvent.total) * 100).toFixed(0);
              if (progress === '100') {
                console.log('切片' + uploadedList[index].Index, progress);
              }
            },
            new axios.CancelToken((cance: any) => {
              cancelTokenMap.set(uploadedList[index].Index, cance);
            })
          )
            .then(async (res: any) => {
              uploadedList[index].status = Status.done;
              cancelTokenMap.delete(uploadedList[index].Index);
              console.log('切片上传成功:' + uploadedList[index].Index, new Date().toLocaleString());
              max++; // 释放通道
              counter++;
              const data = await getUploadProg(props.transId);
              uploadProgress.value = Number((data.prog * 100).toFixed(2));
              if (counter === len) {
                resolve('');
              } else {
                if (stateCode.value !== 3) {
                  start();
                }
              }
            })
            .catch((e) => {
              uploadedList[index].status = Status.error;
              cancelTokenMap.delete(uploadedList[index].Index);
              if (typeof retryArr[index] !== 'number') {
                retryArr[index] = 0;
              }
              console.log(
                '切片上传失败',
                uploadedList[index].Index,
                '重试次数',
                retryArr[index],
                e
              );
              // 次数累加
              retryArr[index]++;
              // 一个请求报错3次的
              if (retryArr[index] >= retrys) {
                return reject(new Error('上传失败'));
              }
              // 3次报错以内的 重启
              max++; // 释放当前占用的通道，但是counter不累加
              if (stateCode.value !== 3) {
                start();
              }
            });
        }
      }
    };
    start();
  });
}

function mergerFileOption() {
  mergerFile({ transId: props.transId }).then((res) => {
    stateCode.value = 1;
    uploadProgress.value = 0;
    getResProgress();
  });
}

function getResProgress() {
  getResoProg(props.resourceId)
    .then((res: any) => {
      stateCodeService.value = res.stateCode;
      if ([21, 22, 23, 24, 32, 33, 42, 43, 52].includes(res.stateCode)) {
        stateCode.value = 1;
        uploadProgress.value = Number((res.prog * 100).toFixed(2));
        timer1 = setTimeout(() => {
          getResProgress();
        }, 2000);
      } else if ([53, 63].includes(res.stateCode)) {
        uploadProgress.value = Number((res.prog * 100).toFixed(2));
        clearTimeout(timer1);
        stateCode.value = 2;
      } else {
        clearTimeout(timer1);
        stateCode.value = 3;
      }
    })
    .catch(() => {
      clearTimeout(timer1);
    });
}
</script>

<style lang="less" scoped>
.progress-wrap {
  height: 100px;
  .title {
    margin-bottom: 10px;
  }
  .state-wrap {
    display: flex;
    margin-top: 10px;
    .ready-text,
    .progress-text,
    .progress-percent {
      font-size: 14px;
      font-weight: 500;
      color: #0560fd;
    }
    .progress-percent {
      margin-left: auto;
    }
    .state {
      display: flex;
      &.state_1 {
        color: #ffa13f;
      }
      &.state_2 {
        color: #24bd48;
      }
      &.state_3 {
        color: #ff5454;
      }
    }
  }
  .option {
    margin-top: 12px;
    text-align: center;
    .el-button {
      width: 113px;
      height: 40px;
      border-radius: 8px;
      border-color: #0066ff;
    }
  }
}

@keyframes dotChange {
  0% {
    width: 4px;
  }

  50% {
    width: 8px;
  }

  100% {
    width: 14px;
  }
}
.dot {
  font-size: 14px;
  animation: dotChange 2s linear 1s infinite;
  overflow: hidden;
}
</style>
<style lang="less">
.custom-dialog-progress {
  .el-dialog__body {
    height: auto !important;
  }
}
</style>
